JdbcTemplate

@VERO
Created Date · 2023년 09월 27일 08:09
Last Updated Date · 2023년 09월 27일 13:09

JDBC 란?

자바에서 데이터베이스에서 접근하기 위한 API 이다.
JDBC 는 관계형 데이터베이스로의 저수준 연결과 SQL 쿼리 실행을 지원하고, Java 애플리케이션과 데이터베이스 사이의 중간다리 역할을 한다.

JdbcTemplate 의 인스턴스는 한 번 설정되면 Thread-safe 하다. Datasource 참조로 직접 인스턴스화해서 서비스 구현에서 사용하거나, ApplicationContext 안에서 준비되고 bean 참조를 통해 사용할 수도 있다.

ApplicationContext 에서 DataSource 는 언제나 빈으로 설정되어야 하고, 첫 번째 경우 (DataSource 참조) 에는 서비스에 직접 제공되고, 두 번째 경우에는 준비된 템플릿으로 제공되어야 한다.

특징

  • JdbcTemplate 은 thread-safe 하다.

JdbcTemplate 의 주요 설정인 DataSource, SQLExceptionTranslator 등은 생성 시점 또는 초기 설정 후에 변경되지 않는다.
또한 JdbcTemplate 은 SQL 과 관련된 로직을 처리하기 위해 콜백 패턴을 사용한다. 사용자는 특정 작업을 위한 콜백 인터페이스를 제공하는데, 콜백들은 각 스레드별로 독립적으로 동작한다.

구성 요소

Connection

데이터베이스 연결을 대표하는 객체이다.
해당 객체를 통해 SQL 명령을 실행하거나, 다른 데이터베이스 작업을 수행한다.

Statement, PreparedStatement

SQL 문을 데이터베이스에 보내기 위한 객체. PreparedStatement 는 파라미터를 동적으로 바인딩해야 하는 SQL 문을 실행하는 데 사용된다.

ResultSet

SQL 쿼리의 결과를 나타내는 객체. 데이터베이스로부터 반환된 데이터를 순차적으로 접근할 수 있다.

SQLException

데이터베이스 작업 중 발생하는 예욀르 처리하는 데 사용되는 예외 클래스이다.

JdbcTemplate 이란?

Spring Framework 에서 제공하는 클래스로, Java 의 JDBC API 를 더 쉽게 효율적으로 사용할 수 있도록 도와준다.

JDBC 의 코드 중복, 오류 처리, 연결 관리 등의 공통 작업들을 단순화 시키며, 개발자는 SQL 쿼리 실행과 관련된 핵심 비즈니스 로직에만 집중할 수 있게 된다.

JdbcAccessor

DataSource 나 exception translator 같은 공통 속성을 정의하는 JdbcTemplate 및 기타 JDBC-accessing DAO helper 들을 위한 base class 이다.

코드는 다음과 같다.

package org.springframework.jdbc.support;  
  
import javax.sql.DataSource;  
  
import org.apache.commons.logging.Log;  
import org.apache.commons.logging.LogFactory;  
  
import org.springframework.beans.factory.InitializingBean;  
import org.springframework.lang.Nullable;  
import org.springframework.util.Assert;  
  
public abstract class JdbcAccessor implements InitializingBean {  
  
    /** Logger available to subclasses. */  
    protected final Log logger = LogFactory.getLog(getClass());  
  
    @Nullable  
    private DataSource dataSource;  
  
    @Nullable  
    private volatile SQLExceptionTranslator exceptionTranslator;  
  
    private boolean lazyInit = true;  
  
  
	public void setDataSource(@Nullable DataSource dataSource) {  
       this.dataSource = dataSource;  
    }  
  
    @Nullable  
    public DataSource getDataSource() {  
       return this.dataSource;  
    }  
  
    protected DataSource obtainDataSource() {  
       DataSource dataSource = getDataSource();  
       Assert.state(dataSource != null, "No DataSource set");  
       return dataSource;  
    }  
  
    public void setDatabaseProductName(String dbName) {  
       if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) {  
          this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);  
       }  
       else {  
          this.exceptionTranslator = new SQLExceptionSubclassTranslator();  
       }  
    }  
    
    public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {  
       this.exceptionTranslator = exceptionTranslator;  
    }  
  
    public SQLExceptionTranslator getExceptionTranslator() {  
       SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;  
       if (exceptionTranslator != null) {  
          return exceptionTranslator;  
       }  
       synchronized (this) {  
          exceptionTranslator = this.exceptionTranslator;  
          if (exceptionTranslator == null) {  
             if (SQLErrorCodeSQLExceptionTranslator.hasUserProvidedErrorCodesFile()) {  
                exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(obtainDataSource());  
             }  
             else {  
                exceptionTranslator = new SQLExceptionSubclassTranslator();  
             }  
             this.exceptionTranslator = exceptionTranslator;  
          }  
          return exceptionTranslator;  
       }  
    }  
    
	public void setLazyInit(boolean lazyInit) {  
       this.lazyInit = lazyInit;  
    }  
  
    public boolean isLazyInit() {  
       return this.lazyInit;  
    }  
  
    @Override  
    public void afterPropertiesSet() {  
       if (getDataSource() == null) {  
          throw new IllegalArgumentException("Property 'dataSource' is required");  
       }  
       if (!isLazyInit()) {  
          getExceptionTranslator();  
       }  
    }  
}
  • setLazyInit : SQLException 를 처음 접했을 때, SQLExceptionTranslator 를 lazy 초기화할지 정한다.

SQLExceptionTranslator 를 지연 초기화 가능하게 했을까?

SQLExceptionTranslator 는 JDBC의 SQLException 을 Spring 의 데이터 접근 계층 예외로 변환하는 역할을 한다.

  1. 모든 사용자가 SQLExceptionTranslator 의 기능을 필요로 하지 않는다. 모든 상황에서 불필요하게 초기화하는 것은 리소스 낭비가 될 수도 있다.
  2. 사용자가 필요에 따라 다른 SQLExceptionTranslator 구현체를 제공할 수 있는 유연성을 제공한다.

JdbcOperations

JDBC 작업을 위한 주요 메서드들을 정의한다.
JdbcTemplateJdbcOperations 의 구현체로, 실제 데이터베이스 작업을 수행한다.

직접적으로 자주 사용되지는 않으나, 쉽게 mocking 하거나 stubbing 할 수 있어 테스트 가용성을 높일 수 있다.

public interface JdbcOperations {
	@Nullable  
	<T> T execute(ConnectionCallback<T> action) throws DataAccessException;

	<T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException;

	@Nullable  
	<T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException;

	Map<String, Object> queryForMap(String sql) throws DataAccessException;

	<T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException;

	int update(String sql) throws DataAccessException;

...
}

queryForObjectquery 같은 메서드들은 Spring 의 JdbcTemplate 과 관련된 편의 메서드들이다.

참고